#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <kewinternal.h>
#include <gmodule.h>

#define SOURCE_FILE_NAME "libkew:misc.c"

static int kew_initialized = FALSE;
KewObject *KewBuiltin[KEW_NUMBER_OF_BUILTINS];

typedef struct _StackFrame StackFrame;

struct _StackFrame {
  StackFrame *next;
  char *source_file_name;
  unsigned line_number;
};

static StackFrame *top_stack_frame;


guint
Kew__HashFunction(gpointer object)
{
  return KewIntVal(KewSend0(object, "hash"));
}


gint
Kew__EqualFunction(gpointer a, gpointer b)
{
  return KewBoolVal(KewSend1(a, "=", b));
}


void
Kew__GenericHash(KewObject *receiver, KewObject *args[], KewObject *results[])
{
  results[0] = KewNewInt(g_direct_hash((gpointer) receiver));
}


void
Kew__GenericEqual(KewObject *receiver, KewObject *args[], KewObject *results[])
{
  results[0] = KewNewBool(receiver == args[0]);
}


void
Kew__GenericFinalize(KewObject *object)
{
}


void
Kew__NoFinalize(KewObject *object)
{
  g_assert_not_reached();
}


char *
KewVersion(void)
{
  return VERSION;
}


char *
KewCopyright(void)
{
  return COPYRIGHT;
}


/* This is the one that's supposed to be here, and should be renamed
 * KewSend.  The other KewSend is deprecated. */

void
KewSend(KewObject *receiver,
        char *selector,
        KewObject *args[],
        unsigned number_of_args,
        KewObject *results[],
        unsigned number_of_results,
        char *source_file_name,
        unsigned line_number)
{
  StackFrame stack_frame;
  KewDispatchMap *dispatch_mapping;
  char *exception_message;

  stack_frame.next = top_stack_frame;
  stack_frame.source_file_name = source_file_name;
  stack_frame.line_number = line_number;
  top_stack_frame = &stack_frame;

  dispatch_mapping = receiver->type->dispatch_map;
  
  while (dispatch_mapping->selector != NULL) {
    if (strcmp(dispatch_mapping->selector, selector) == 0 &&
        dispatch_mapping->argument_cardinality == number_of_args &&
        dispatch_mapping->result_cardinality == number_of_results)
      {
        (*dispatch_mapping->method)(receiver, args, results);
        top_stack_frame = top_stack_frame->next;
        return;
      }
    dispatch_mapping++;
  }

  exception_message = g_strdup_printf("Object '%s:%u' does not understand message '%s:%u:%u'",
                                      receiver->type->source_file_name,
                                      receiver->type->line_number,
                                      selector,
                                      number_of_args,
                                      number_of_results);
  KewSend1(KewMissingMethod, "raise", KewNewString(exception_message));
  top_stack_frame = top_stack_frame->next;
  return;
}


KewObject *
_KewSend0(KewObject *receiver,
          char *selector,
          char *source_file_name,
          unsigned line_number)
{
  KewObject *send_results[1];
  KewSend(receiver, selector, NULL, 0, send_results, 1, source_file_name, line_number);
  return send_results[0];
}


KewObject *
_KewSend1(KewObject *receiver,
          char *selector,
          KewObject *arg0,
          char *source_file_name,
          unsigned line_number)
{
  KewObject *send_args[1];
  KewObject *send_results[1];

  send_args[0] = arg0;
  KewSend(receiver, selector, send_args, 1, send_results, 1, source_file_name, line_number);
  return send_results[0];
}


KewObject *
_KewSend2(KewObject *receiver,
          char *selector,
          KewObject *arg0,
          KewObject *arg1,
          char *source_file_name,
          unsigned line_number)
{
  KewObject *send_args[2];
  KewObject *send_results[1];
  send_args[0] = arg0;
  send_args[1] = arg1;
  KewSend(receiver, selector, send_args, 2, send_results, 1, source_file_name, line_number);
  return send_results[0];
}


KewObject *
_KewSend3(KewObject *receiver,
          char *selector,
          KewObject *arg0,
          KewObject *arg1,
          KewObject *arg2,
          char *source_file_name,
          unsigned line_number)
{
  KewObject *send_args[3];
  KewObject *send_results[1];
  send_args[0] = arg0;
  send_args[1] = arg1;
  send_args[2] = arg2;
  KewSend(receiver, selector, send_args, 3, send_results, 1, source_file_name, line_number);
  return send_results[0];
}


KewObject *
_KewSend4(KewObject *receiver,
          char *selector,
          KewObject *arg0,
          KewObject *arg1,
          KewObject *arg2,
          KewObject *arg3,
          char *source_file_name,
          unsigned line_number)
{
  KewObject *send_args[4];
  KewObject *send_results[1];
  send_args[0] = arg0;
  send_args[1] = arg1;
  send_args[2] = arg2;
  send_args[3] = arg3;
  KewSend(receiver, selector, send_args, 4, send_results, 1, source_file_name, line_number);
  return send_results[0];
}


KewObject *
_KewSend5(KewObject *receiver,
          char *selector,
          KewObject *arg0,
          KewObject *arg1,
          KewObject *arg2,
          KewObject *arg3,
          KewObject *arg4,
          char *source_file_name,
          unsigned line_number)
{
  KewObject *send_args[5];
  KewObject *send_results[1];
  send_args[0] = arg0;
  send_args[1] = arg1;
  send_args[2] = arg2;
  send_args[3] = arg3;
  send_args[4] = arg4;
  KewSend(receiver, selector, send_args, 5, send_results, 1, source_file_name, line_number);
  return send_results[0];
}


KewObject *
_KewSend6(KewObject *receiver,
          char *selector,
          KewObject *arg0,
          KewObject *arg1,
          KewObject *arg2,
          KewObject *arg3,
          KewObject *arg4,
          KewObject *arg5,
          char *source_file_name,
          unsigned line_number)
{
  KewObject *send_args[6];
  KewObject *send_results[1];
  send_args[0] = arg0;
  send_args[1] = arg1;
  send_args[2] = arg2;
  send_args[3] = arg3;
  send_args[4] = arg4;
  send_args[5] = arg5;
  KewSend(receiver, selector, send_args, 6, send_results, 1, source_file_name, line_number);
  return send_results[0];
}


void
KewLoadComponent(char *path, 
                 char *component_name,
                 KewObject *args[],
                 unsigned number_of_args,
                 KewObject *results[],
                 unsigned number_of_results)
{
  char buffer[PATH_MAX+1];
  char *dll_file_name;
  GModule *dll;

  /* FIXME: raise exceptions instead of quitting container */
  KewObject *(*get_component)(KewObject *args[], unsigned number_of_args);
  int lookup_successful;

  /* Resolve NULL paths to current directory */
  if (path == NULL)
    path = getcwd(buffer, PATH_MAX);

  /* Link the DLL */
  dll_file_name = g_module_build_path(path, component_name);
  dll = g_module_open(dll_file_name, 0);
  if (dll == NULL) {
    KewPrintStackTrace();
    g_error("Component '%s' could not be opened\n"
            "- maybe there is no '%s' file\n"
            "- maybe it has undefined symbols and won't link correctly\n",
            component_name,
            dll_file_name);
    return;
  }

  /* Find the KewGetComponent symbol (entry point of top-level block) */
  lookup_successful = g_module_symbol(dll,
                                      "KewGetModule",
                                      (gpointer) &get_component);
  if (!lookup_successful) {
    g_error("Component '%s' loaded but had no 'KewGetModule' function\n"
            "- maybe it is not a kew component, but just an ordinary dynamic library?\n",
            component_name);
    return;
  }

  /* Run the component's top-level block and hold component object */
  /* FIXME: either check cardinality here or pass it in so component can. */
  get_component(args, results);
}


KewObject *
KewNewObjectFull(KewObjectType *type,
                 unsigned context_size)
{
  KewObject *result = g_malloc(G_STRUCT_OFFSET(KewObject, context)
                               + (context_size * sizeof(KewObject *)));
  
  result->type = type;
  return result;
}


KewObject *
KewNewObject(KewObjectType *type)
{
  return KewNewObjectFull(type, type->context_slots);
}


KewObject *
KewCheckType(KewObject *object, KewObjectType *object_type, char *type_name)
{
  KewObject *message = KewWrapString(g_strconcat("Type mismatch: expected a ", type_name, NULL));

  while (object->type != object_type) {
    object = KewSend1(KewWrongObjectType, "raise", message);
  }

  return object;
}


void
Kew__InitMisc(void)
{
}


void KewPrintStackTrace(void)
{
  StackFrame *current_frame = top_stack_frame;

  fprintf(stderr, "*** STACK TRACE ***\n");

  while (current_frame) {
    fprintf(stderr, "%s:%u\n",
            current_frame->source_file_name,
            current_frame->line_number);
    current_frame = current_frame->next;
  }
}


void
KewInit(void)
{
  if (! kew_initialized) {
    top_stack_frame = NULL;
    Kew__InitMisc();
    Kew__InitException();
    Kew__InitContinuation();
    Kew__InitNil();
    Kew__InitBool();
    Kew__InitVar();
    Kew__InitList();
    Kew__InitMap();
    Kew__InitMessage();
    Kew__InitPrimitives();
    kew_initialized = TRUE;
  }
}


